package com.nulldev.util.web.YouTubeExtractor.Qtraxtor;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.VariableAPI.util.strings.splitters.third_party.Splitter;
import com.nulldev.util.data.Arrays.arrays.FastArrayList;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.MarkedForRemoval;
import com.nulldev.util.manage.Unimplemented;

@MarkedForRemoval(reason = "Replaced by URLUtils' query parsing API.", plannedRemovalWhen = "2.3 or 2.2.10")
public class Qtraxtor {

	public static volatile boolean ENABLE_BLANK_SEQUENCE_SKIP = false;

	public static enum SplitMode {
		STANDARD,
		YOUTUBE_EXTRACTOR,
		YOUTUBE_EXTRACTOR_WITH_ALT_VALUE_HANDLE,
		@Deprecated
		EXPERIMENTAL,
	}

	public static enum FillMode {
		/**
		 * Default. <br>
		 * Checks if there already exists a value and if so, creates a new sets and adds
		 * to it. <br>
		 * Else it will add the element.
		 */
		COLLISION_CHECK,
		/**
		 * Experimental. <br>
		 * If a collision is met, it adds it to a new element.
		 */
		@Deprecated
		@Unimplemented
		FILL_OR_CREATE_AND_FILL,
		FILL_INTO_SINGLE_SET,
	}

	private static final Logger log = LoggerFactory.getLoggerD(true);

	public static List<ExtractedSet> get(final String query, final String... keyCacheExemptions) {
		return get(query, SplitMode.STANDARD, keyCacheExemptions);
	}

	public static List<ExtractedSet> get(final String query, final SplitMode splitStyle, final String... keyCacheExemptions) {
		return get(query, splitStyle, FillMode.COLLISION_CHECK, keyCacheExemptions);
	}

	private static final Splitter SPLITTER_DEFAULT = Splitter.onPattern("\\?|\\&");
	private static final Splitter SPLITTER_EXPERIMENTAL = Splitter.on("\\u0026");
	private static final Splitter SPLITTER_YTE = Splitter.onPattern("(,(?=\\S))|\\?|\\&");
	private static final Splitter SPLITTER_YTE_ALV = Splitter.onPattern("(,(?=\\S))");

	public static List<ExtractedSet> get(final String query, final SplitMode splitStyle, final FillMode fillMode, final String... keyCacheExemptions) {
		final FastArrayList<ExtractedSet> es = new FastArrayList<ExtractedSet>(true);
		if (query == null || query.isEmpty())
			return Lists.of();
		// This resets on collision. We then add the set. If it is the same value, we
		// don't add and don't reset.
		String[] _splitQuery = null;
		final boolean enableYTStyleSplit = (splitStyle == SplitMode.YOUTUBE_EXTRACTOR || splitStyle == SplitMode.YOUTUBE_EXTRACTOR_WITH_ALT_VALUE_HANDLE);
		if (splitStyle == SplitMode.EXPERIMENTAL) {
			_splitQuery = SPLITTER_EXPERIMENTAL.splitToArray(query); // query.split(Pattern.quote("\\u0026"));
		} else if (splitStyle == SplitMode.YOUTUBE_EXTRACTOR) {
			_splitQuery = SPLITTER_YTE.splitToArray(query); // query.split("(,(?=\\S))|\\?|\\&");
		} else if (splitStyle == SplitMode.YOUTUBE_EXTRACTOR_WITH_ALT_VALUE_HANDLE) {
			_splitQuery = SPLITTER_YTE_ALV.splitToArray(query); // query.split("(,(?=\\S))");
		} else {
			_splitQuery = SPLITTER_DEFAULT.splitToArray(query); // query.split("\\?|\\&");
		}
		final Map<String, String> keyCache = FastMaps.map(_splitQuery.length);
		final String[] splitQuery = _splitQuery;
		// = (enableYTStyleSplit && query.contains(",")
		// ? query.split("(,(?=\\S))|\\?|\\&") : query.split("\\?|\\&"));
		if (es.isEmpty()) {
			es.add(new ExtractedSet());
		}
		if (splitStyle == SplitMode.YOUTUBE_EXTRACTOR_WITH_ALT_VALUE_HANDLE) {
			boolean __firstLoop = false;
			for (final String q : splitQuery) {
				final String[] preValues = SPLITTER_DEFAULT.splitToArray(q); // q.split("\\?|\\&");
				if (fillMode == FillMode.FILL_INTO_SINGLE_SET) {
					if (__firstLoop) {
						es.add(new ExtractedSet());
					}
				}
				for (final String entry : preValues) {
					final String[] values = Splitters.splitString(entry, "=");
					if (values.length == 0)
						continue;
					__process_kv(values[0], (values.length == 1 ? "" : values[1]), values, keyCache, es, enableYTStyleSplit, keyCacheExemptions, fillMode);
				}
				if (!__firstLoop) {
					__firstLoop = true;
				}
				// final String[] values = q.split("=");
				// __process_kv(values[0], (values.length == 1 ? "" : values[1]),
				// values, keyCache, es, enableYTStyleSplit, keyCacheExemptions);
			}
		} else {
			for (final String q : splitQuery) {
				final String[] values = Splitters.splitString(q, "=");
				if (values.length == 0)
					continue;
				__process_kv(values[0], (values.length == 1 ? "" : values[1]), values, keyCache, es, enableYTStyleSplit, keyCacheExemptions, fillMode);
			}
		}
		return es;
	}

	private static void __process_kv(final String key, final String value, final String[] values, final Map<String, String> keyCache,
			final FastArrayList<ExtractedSet> es, final boolean enableYTStyleSplit, final String[] keyCacheExemptions, final FillMode mode) {
		if (mode.equals(FillMode.COLLISION_CHECK)) {
			try {
				// final String key = values[0];
				// log.info(key + ":" + ArrayUtils.StringArrayUtils.addFrom(values, 1));
				if (values.length == 1) {
					return;
				}
				// final String value = values[1];
				if (keyCache.containsKey(key)) {
					if (keyCache.get(key).equals(value) && !ArrayUtils.contains(keyCacheExemptions, key)) {
						// IGNORE
					} else {
						keyCache.clear();
						// es.getLast().get().putIfAbsent(key, value);
						es.add(new ExtractedSet());
						es.getLast().add(key, value);
						keyCache.put(key, value);
					}
				} else {
					if ((key.equals("type") || key.equals("codecs")) && enableYTStyleSplit) {
						if (key.equals("type")) {
							String extra = "";
							if (values.length > 2) {
								extra += "=" + values[2];
							}
							es.getLast().add(key, value + extra);
							keyCache.put(key, value + extra);
						} else {
							final String previous = es.getLast().get().get("key");
							String extra = "";
							if (values.length > 2) {
								extra += "=" + values[2];
							}
							es.getLast().add(key, previous + value + extra);
							keyCache.put(key, previous + value + extra);
						}
					} else if (key.equals("s") && enableYTStyleSplit && values.length > 2) {
						String extra = "";
						for (int i = 2; i < values.length; i++) {
							if (values[i].isEmpty() && ENABLE_BLANK_SEQUENCE_SKIP) {
								log.warn("Skipping blank sequence.");
								continue;
							}
							extra += "=" + values[i];
						}
						log.info("fixed sig: " + (value + extra));
						es.getLast().add(key, value + extra);
						keyCache.put(key, value + extra);
					} else {
						es.getLast().add(key, value);
						keyCache.put(key, value);
					}
				}
			} catch (Exception e) {
				log.error("Failed to read value: " + Arrays.toString(values), e);
			}
		} else if (mode == FillMode.FILL_INTO_SINGLE_SET) {
			try {
				String finalValue = value;
				if ((key.equals("type") || key.equals("codecs")) && enableYTStyleSplit) {
					if (key.equals("type")) {
						String extra = "";
						if (values.length > 2) {
							extra += "=" + values[2];
						}
						finalValue = value + extra;
					} else {
						final String previous = es.getLast().get().get("key");
						String extra = "";
						if (values.length > 2) {
							extra += "=" + values[2];
						}
						finalValue = previous + value + extra;
					}
				} else if (key.equals("s") && enableYTStyleSplit && values.length > 2) {
					String extra = "";
					for (int i = 2; i < values.length; i++) {
						extra += "=" + values[i];
					}
					log.info("fixed sig: " + (value + extra));
					finalValue = value + extra;
				}
				es.getLast().add(key, finalValue);
			} catch (Exception e) {
				log.error("Failed to read value: " + Arrays.toString(values), e);
			}
		} else {
			try {
				String finalValue = value;
				if ((key.equals("type") || key.equals("codecs")) && enableYTStyleSplit) {
					if (key.equals("type")) {
						String extra = "";
						if (values.length > 2) {
							extra += "=" + values[2];
						}
						finalValue = value + extra;
					} else {
						final String previous = es.getLast().get().get("key");
						String extra = "";
						if (values.length > 2) {
							extra += "=" + values[2];
						}
						finalValue = previous + value + extra;
					}
				} else if (key.equals("s") && enableYTStyleSplit && values.length > 2) {
					String extra = "";
					for (int i = 2; i < values.length; i++) {
						extra += "=" + values[i];
					}
					log.info("fixed sig: " + (value + extra));
					finalValue = value + extra;
				}
				boolean _regOpSuccessful = false;
				if (es.isEmpty()) {
					es.add(new ExtractedSet());
				}
				for (int i = 0; i < es.size(); i++) {
					final ExtractedSet set = es.get(i);
					if (set.get().containsKey(key)) {
						continue;
					}
					if (set.__add_if_possible(key, finalValue)) {
						log.debug("set " + i + ": added: " + key + ": " + value);
						_regOpSuccessful = true;
						break;
					} else {
						continue;
					}
				}
				if (!_regOpSuccessful) {
					es.add(new ExtractedSet());
					if (!es.getLast().__add_if_possible(key, finalValue)) {
						log.error("__process_kv::FILL_OR_CREATE_AND_FILL -> Fallback was refused!");
					}
				}
			} catch (Exception e) {
				log.error("Failed to read value: " + Arrays.toString(values), e);
			}
		}
	}

	public static void main(String[] args) {
		ArrayUtils.printLln(get("?a=1&b=2&c=4&d=5"));
	}
}
